/*
 * This file is part of "IdeWiki" project.
 *
 * Copyright (c) 2008 Hendrik Ebel
 * All Rights Reserved.
 *
 * Please visit http://code.google.com/p/idewiki for updates and contact.
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 * --LICENSE NOTICE--
 */
package ws.ebel.idewiki.editors;

import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.TextViewerUndoManager;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Text;

import ws.ebel.idewiki.Activator;
import ws.ebel.idewiki.browser.IdeWikiBrowser;
import ws.ebel.idewiki.browser.ViewType;
import ws.ebel.idewiki.xmlrpc.RpcClient;
import ws.ebel.idewiki.xmlrpc.SnipBean;

/**
 * The Class IdeWikiEditor.
 * 
 * @author Hendrik Ebel
 */
public class IdeWikiEditor {

    /** The location. */
    private String location;

    /** The editor area. */
    private Composite editorArea;

    /** The snip. */
    private SnipBean snip;

    /** The tab item. */
    private CTabItem tabItem;

    /** The snip detail label. */
    private Label snipDetailLabel;

    /** The tab folder. */
    private CTabFolder tabFolder;

    /** The source viewer. */
    private SourceViewer sourceViewer;

    /** The view type. */
    private ViewType viewType;

    /** The new parent name text. */
    private Text newParentNameText;

    /** The new snip name text. */
    private Text newSnipNameText;

    /** is true if editor text changed */
    private boolean changeFlag = false;

    private Button saveButton;

    private Button abortButton;
    
    private Button newButton;
    
    private Button previewButton;
    
    private Button helpButton;

    /**
     * The Constructor.
     * 
     * @param tabItem the tab item
     * @param snip the snip
     * @param location the location
     * @param tabFolder the tab folder
     * @param viewType the view type
     */
    public IdeWikiEditor(CTabFolder tabFolder, CTabItem tabItem, String location, SnipBean snip, ViewType viewType) {
        super();
        this.location = location;
        this.snip = snip;
        this.tabItem = tabItem;
        this.tabFolder = tabFolder;
        this.viewType = viewType;
        createPartControl(tabFolder, snip);
    }

    /**
     * Creates the part control.
     * 
     * @param parentTabFolder the parent tab folder
     * @param snip the snip
     */
    private void createPartControl(Composite parentTabFolder, SnipBean snip) {
        editorArea = new Composite(parentTabFolder, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 3;
        gridLayout.marginHeight = 2;
        gridLayout.marginWidth = 2;
        gridLayout.verticalSpacing = 2;
        editorArea.setLayout(gridLayout);
        editorArea.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
        if (viewType == ViewType.EditView) {
            createSnipTitleLable(snip);
            createSnipDetailLable(snip);
        } else if (viewType == ViewType.NewView) {
            if (snip.getSnipName().length() == 0) {
                createParentSnipName();
            }
            createNewSnipTitle();
        } else {
            Activator.getDefault().reportError("Error", "ViewType not supported!");
        }

        createSnipViewer(snip);
        createButtons();
    }

    /**
     * Creates the new snip title.
     * 
     * @param aSnip      */
    private void createNewSnipTitle() {
        Composite newSnipComposite = new Composite(editorArea, SWT.NONE);
        GridLayout newSnipLayout = new GridLayout();
        newSnipLayout.numColumns = 2;
        newSnipLayout.marginWidth = 0;
        newSnipLayout.marginHeight = 3;
        newSnipComposite.setLayout(newSnipLayout);
        newSnipComposite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER | GridData.FILL_HORIZONTAL));

        Label newSnipNameLabel = new Label(newSnipComposite, SWT.HORIZONTAL | SWT.LEFT);
        newSnipNameLabel.setFont(getNewFont(newSnipNameLabel, 10));
        newSnipNameLabel.setText("New Snip Name: ");
        newSnipNameText = new Text(newSnipComposite, SWT.SINGLE | SWT.LEFT | SWT.BORDER);

        GridData labeldata = new GridData();
        newSnipNameLabel.setLayoutData(labeldata);

        GridData textdata = new GridData();
        textdata.horizontalAlignment = GridData.FILL;
        textdata.grabExcessHorizontalSpace = true;
        textdata.horizontalSpan = 1;
        textdata.horizontalIndent = 2;
        newSnipNameText.setLayoutData(textdata);
        newSnipNameText.setFocus();
        newSnipNameText.setFont(getNewFont(newSnipNameText, 10));
        newSnipNameText.setText(snip.getDecodeSnipName());
    }

    /**
     * Creates the parent snip name.
     * 
     * @param aSnip      */
    private void createParentSnipName() {
        Composite parentComposite = new Composite(editorArea, SWT.NONE);
        GridLayout parentLayout = new GridLayout();
        parentLayout.numColumns = 2;
        parentLayout.marginWidth = 3;
        parentLayout.marginHeight = 3;
        parentComposite.setLayout(parentLayout);
        parentComposite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER | GridData.FILL_HORIZONTAL));

        Label newParentNameLabel = new Label(parentComposite, SWT.HORIZONTAL | SWT.LEFT);
        newParentNameLabel.setText("Parent: ");
        newParentNameLabel.setFont(getNewFont(newParentNameLabel, 10));
        newParentNameText = new Text(parentComposite, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
        GridData labledata = new GridData();
        newParentNameLabel.setLayoutData(labledata);

        GridData textdata = new GridData();
        textdata.horizontalAlignment = GridData.FILL;
        textdata.grabExcessHorizontalSpace = true;
        textdata.horizontalSpan = 1;
        textdata.horizontalIndent = 2;
        newParentNameText.setLayoutData(textdata);
        newParentNameText.setFont(getNewFont(newParentNameText, 10));
        newParentNameText.setText(snip.getDecodeParentSnipName());
    }

    /**
     * Gets the new font.
     * 
     * @param height the height
     * @param control the control
     * @param newParentNameText      * 
     * @return Font
     */
    private Font getNewFont(Control control, int height) {
        Font initialFont = control.getFont();
        FontData[] fontData = initialFont.getFontData();
        for (int i = 0; i < fontData.length; i++) {
            fontData[i].setHeight(height);
        }
        Font biggerFont = new Font(editorArea.getDisplay(), fontData);
        return biggerFont;
    }

    /**
     * Creates the snip viewer.
     * 
     * @param snip the snip
     */
    private void createSnipViewer(SnipBean snip) {
        GridData data = new GridData(GridData.FILL_BOTH);
        data.grabExcessHorizontalSpace = true;
        data.grabExcessVerticalSpace = true;
        data.horizontalSpan = 3;

        sourceViewer = new SourceViewer(editorArea, null, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);

        Document snipDoc = new Document(snip.getContent());
        TextViewerUndoManager undoManager = new TextViewerUndoManager(30);
        sourceViewer.setUndoManager(undoManager);
        undoManager.connect(sourceViewer);
        sourceViewer.setDocument(snipDoc);
        sourceViewer.setData("snipBean", snip);
        sourceViewer.setData("tabItem", tabItem);
        sourceViewer.setEditable(true);
        sourceViewer.getTextWidget().setLayoutData(data);
        sourceViewer.getTextWidget().setIndent(5);
        sourceViewer.getTextWidget().setFont(new Font(editorArea.getDisplay(), Activator.getDefault().getWikiEditorFontPreference()));
        sourceViewer.configure(new IdeWikiEditorConfiguration());

        sourceViewer.getDocument().addDocumentListener(new IDocumentListener() {
            public void documentAboutToBeChanged(DocumentEvent aEvent) {
            }

            public void documentChanged(DocumentEvent aEvent) {
                changeFlag = true;
            }
        });

        sourceViewer.getControl().addKeyListener(new KeyAdapter() {
            /**
             * @see org.eclipse.swt.events.KeyAdapter#keyReleased(org.eclipse.swt.events.KeyEvent)
             */
            @Override
            public void keyReleased(KeyEvent aEvent) {
                super.keyReleased(aEvent);

                boolean isCtrlS = (aEvent.stateMask == SWT.CTRL) && (aEvent.keyCode == 115);

                if (isCtrlS) {
                    if(newButton == null){
                        IdeWikiEditor.this.saveAction((SnipBean) sourceViewer.getData("snipBean"));
                        aEvent.doit = false;
                    } else {
                        IdeWikiEditor.this.createAction((SnipBean) sourceViewer.getData("snipBean"));
                        aEvent.doit = false;
                    }
                } 
            }

            /**
             * @see org.eclipse.swt.events.KeyAdapter#keyPressed(org.eclipse.swt.events.KeyEvent)
             */
            @Override
            public void keyPressed(KeyEvent aEvent) {
                boolean isCtrlX = (aEvent.stateMask == SWT.CTRL) && (aEvent.keyCode == 120);
                boolean isCtrlY = (aEvent.stateMask == SWT.CTRL) && (aEvent.keyCode == 121);
                boolean isCtrlZ = (aEvent.stateMask == SWT.CTRL) && (aEvent.keyCode == 122);
                boolean isCtrlA = (aEvent.stateMask == SWT.CTRL) && (aEvent.keyCode == 97);
                //boolean isCtrlC = (event.stateMask == SWT.CTRL) && (event.keyCode == 99);
                //boolean isCtrlV = (event.stateMask == SWT.CTRL) && (event.keyCode == 118);
                boolean isCtrlSpace = aEvent.stateMask == SWT.CTRL && aEvent.character == ' ';

                // Check for Ctrl+Spacebar
                if (isCtrlSpace) {
                    // Check if source viewer is able to perform operation
                    if (sourceViewer.canDoOperation(SourceViewer.CONTENTASSIST_PROPOSALS)) {
                        // Perform operation
                        sourceViewer.doOperation(SourceViewer.CONTENTASSIST_PROPOSALS);
                    }
                    // Veto this key press to avoid further processing
                    aEvent.doit = false;
                } else if (isCtrlX) {
                    if (sourceViewer.canDoOperation(SourceViewer.CUT)) {
                        sourceViewer.doOperation(SourceViewer.CUT);
                    }
                    aEvent.doit = false;
                } else if (isCtrlZ) {
                    if (sourceViewer.canDoOperation(SourceViewer.UNDO)) {
                        sourceViewer.doOperation(SourceViewer.UNDO);
                    }
                    aEvent.doit = false;
                } else if (isCtrlY) {
                    if (sourceViewer.canDoOperation(SourceViewer.REDO)) {
                        sourceViewer.doOperation(SourceViewer.REDO);
                    }
                    aEvent.doit = false;
                } else if (isCtrlA) {
                    if (sourceViewer.canDoOperation(SourceViewer.SELECT_ALL)) {
                        sourceViewer.doOperation(SourceViewer.SELECT_ALL);
                    }
                    aEvent.doit = false;
                }
            }
        });

        sourceViewer.enableOperation(SourceViewer.CUT, true);
        sourceViewer.enableOperation(SourceViewer.UNDO, true);
        sourceViewer.enableOperation(SourceViewer.REDO, true);
        sourceViewer.enableOperation(SourceViewer.SELECT_ALL, true);
    }

    /**
     * Creates the buttons.
     */
    private void createButtons() {
        
        // createHelpButton();
        // createPreviewButton();
        
        if (viewType == ViewType.EditView) {
            createSaveButton();
        } else {
            createNewButton();
        }
        createAbortButton();
    }

    /**
     * TODO: add preview feature ;-)
     */
    private void createPreviewButton() {
    }

    /**
     * Creates the Help button.
     */
    private void createHelpButton() {
        helpButton = new Button(editorArea, SWT.PUSH);
        helpButton.setText(" Help ");
        GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
        data.horizontalSpan = 2;
        data.grabExcessHorizontalSpace = true;
        data.horizontalIndent = 5;
        data.verticalIndent = 2;
        helpButton.setLayoutData(data);
        helpButton.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                if (event.widget == helpButton) {
                    System.out.println("hgelp button");
                    helpAction();
                }
            }

            private void helpAction() {
                
            }
        });
    }

    /**
     * Creates the new button.
     */
    private void createNewButton() {
        newButton = new Button(editorArea, SWT.PUSH);
        newButton.setText(" Create ");
        GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
        data.horizontalSpan = 2;
        data.grabExcessHorizontalSpace = true;
        data.horizontalIndent = 5;
        data.verticalIndent = 2;
        newButton.setLayoutData(data);
        newButton.setData(snip);

        if (newSnipNameText != null) {
            newButton.addListener(SWT.Selection, new Listener() {
                public void handleEvent(Event event) {
                    if (event.widget == newButton) {
                        createAction((SnipBean) newButton.getData());
                    }
                }
            });
        } else {
            Activator.getDefault().reportError("Error", "snip name text field is NULL");
        }
    }

    /**
     * Gets the new snip name.
     * 
     * @return the new snip name
     */
    private String getNewSnipName() {
        if (newSnipNameText.getText().length() == 0 || newSnipNameText.getText().contains("+")) {
            return null;
        }

        String result = null;

        if (newParentNameText == null) {
            result = newSnipNameText.getText();
        } else if (newParentNameText.getText().endsWith("/") || newParentNameText.getText().length() == 0) {
            result = newParentNameText.getText() + newSnipNameText.getText();
        } else {
            result = newParentNameText.getText() + "/" + newSnipNameText.getText();
        }
        return result;
    }

    /**
     * Creates the abort button.
     */
    private void createAbortButton() {
        abortButton = new Button(editorArea, SWT.PUSH);
        abortButton.setText("Cancel");
        GridData abortLayoutData = new GridData(GridData.HORIZONTAL_ALIGN_END);
        abortLayoutData.horizontalIndent = 5;
        abortLayoutData.verticalIndent = 2;
        abortButton.setLayoutData(abortLayoutData);
        abortButton.setData(tabItem);

        abortButton.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                if (event.widget == abortButton && changeFlag == false) {
                    IdeWikiEditor.this.closeAction((CTabItem) abortButton.getData());
                } else if (event.widget == abortButton && changeFlag == true) {
                    MessageBox messageBox = new MessageBox(editorArea.getShell(), SWT.ICON_WARNING | SWT.YES | SWT.NO
                            | SWT.CANCEL);
                    messageBox.setText("Warning");
                    messageBox.setMessage("Save the changes before exiting?");
                    int buttonID = messageBox.open();
                    switch (buttonID) {
                    case SWT.YES:
                        IdeWikiEditor.this.saveAction((SnipBean) saveButton.getData());
                        break;
                    case SWT.NO:
                        IdeWikiEditor.this.closeAction((CTabItem) abortButton.getData());
                        break;
                    case SWT.CANCEL:
                        // does nothing ...
                        event.doit = false;
                    }
                }
            }

        });
    }

    /**
     * Creates the save button.
     */
    private void createSaveButton() {
        saveButton = new Button(editorArea, SWT.PUSH);
        saveButton.setText(" Save ");
        GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END);
        data.horizontalSpan = 2;
        data.grabExcessHorizontalSpace = true;
        data.horizontalIndent = 5;
        data.verticalIndent = 2;
        saveButton.setLayoutData(data);
        saveButton.setData(snip);

        saveButton.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                if (event.widget == saveButton) {
                    IdeWikiEditor.this.saveAction((SnipBean) saveButton.getData());
                }
            }

        });
    }

    /**
     * @param selTabItem
     */
    public void closeAction(final CTabItem selTabItem) {
        IdeWikiEditor wikiEditor = (IdeWikiEditor) selTabItem.getData();
        CTabItem browserTab = tabFolder.getItem(0);
        String url = Activator.getDefault().getWikiUrlPreference() + "/space/" + wikiEditor.getSnip().getFullEncodeSnipName();
        IdeWikiBrowser wikiBrowser = (IdeWikiBrowser) browserTab.getData();
        wikiBrowser.openLocation(url);
        wikiBrowser.refresh();
        Activator.getDefault().removeWikiEditor((IdeWikiEditor) selTabItem.getData());
        selTabItem.dispose();
    }

    /**
     * @param snipbean
     */
    public void saveAction(final SnipBean snipbean) {
        snipbean.setContent(sourceViewer.getDocument().get());
        RpcClient.getInstance().updateSnip(snipbean);
        CTabItem browserTab = tabFolder.getItem(0);
        String url = Activator.getDefault().getWikiUrlPreference() + "/space/" + snipbean.getFullEncodeSnipName();
        IdeWikiBrowser wikiBrowser = (IdeWikiBrowser) browserTab.getData();
        wikiBrowser.openLocation(url);
        wikiBrowser.refresh();
        tabFolder.setSelection(0);
        Activator.getDefault().removeWikiEditor((IdeWikiEditor) tabItem.getData());
        tabItem.dispose();
    }
    
    /**
     * @param newButton
     */
    private void createAction(final SnipBean snipBean) {
        String newSnipName = getNewSnipName();
        String newContent = sourceViewer.getDocument().get();

        if (newSnipName != null) {
            snipBean.setContent(newContent);
            snipBean.setSnipName(newSnipName);
            RpcClient.getInstance().createSnip(snipBean);
            CTabItem browserTab = tabFolder.getItem(0);
            String url = Activator.getDefault().getWikiUrlPreference() + "/space/"
                    + snipBean.getFullEncodeSnipName();
            IdeWikiBrowser wikiBrowser = (IdeWikiBrowser) browserTab.getData();
            wikiBrowser.openLocation(url);
            tabFolder.setSelection(0);
            Activator.getDefault().removeWikiEditor((IdeWikiEditor) tabItem.getData());
            tabItem.dispose();
        } else {
            Activator.getDefault().reportError("Error", "Snip name must be correct!");
        }
    }

    /**
     * Creates the snip detail lable.
     * 
     * @param snip the snip
     */
    private void createSnipDetailLable(SnipBean snip) {
        snipDetailLabel = new Label(editorArea, SWT.HORIZONTAL | SWT.LEFT);

        GridData labledata = new GridData();
        labledata.horizontalAlignment = GridData.FILL;
        labledata.grabExcessHorizontalSpace = true;
        labledata.horizontalSpan = 3;
        labledata.horizontalIndent = 2;
        snipDetailLabel.setLayoutData(labledata);

        Font initialFont = snipDetailLabel.getFont();
        FontData[] fontData = initialFont.getFontData();
        for (int i = 0; i < fontData.length; i++) {
            fontData[i].setHeight(fontData[i].getHeight() - 1);
        }
        Font smalerFont = new Font(editorArea.getDisplay(), fontData);
        snipDetailLabel.setFont(smalerFont);
        snipDetailLabel.setText(createDetailString(snip));
    }

    /**
     * Creates the detail string.
     * 
     * @param snip the snip
     * 
     * @return the string
     */
    private String createDetailString(SnipBean snip) {
        StringBuffer detailContent = new StringBuffer();
        detailContent.append("Created by: ");
        detailContent.append(snip.getCUser());
        detailContent.append(" (");
        detailContent.append(snip.getFormattedCTime());
        detailContent.append(")");
        detailContent.append("  --  Last edited by: ");
        detailContent.append(snip.getMUser());
        detailContent.append(" (");
        detailContent.append(snip.getFormattedMTime());
        detailContent.append(")");
        return detailContent.toString();
    }

    /**
     * Creates the snip title lable.
     * 
     * @param snip the snip
     */
    private void createSnipTitleLable(SnipBean snip) {
        Label snipLable = new Label(editorArea, SWT.HORIZONTAL | SWT.LEFT);

        GridData labledata = new GridData();
        labledata.horizontalAlignment = GridData.FILL;
        labledata.grabExcessHorizontalSpace = true;
        labledata.horizontalSpan = 3;
        labledata.horizontalIndent = 2;
        snipLable.setLayoutData(labledata);

        Font initialFont = snipLable.getFont();
        FontData[] fontData = initialFont.getFontData();
        for (int i = 0; i < fontData.length; i++) {
            fontData[i].setHeight(14);
        }
        Font biggerFont = new Font(editorArea.getDisplay(), fontData);
        snipLable.setFont(biggerFont);
        snipLable.setText(snip.getDecodeSnipName());
    }

    /**
     * Update snip edit widget and detail lable.
     * 
     * @param snip the snip
     */
    public void updateSnipContent(SnipBean snip) {
        if (sourceViewer != null && snip != null) {
            sourceViewer.setDocument(new Document(snip.getContent()));
            snipDetailLabel.setText(createDetailString(snip));
        } else {
            Activator.getDefault().reportError("IdeWikiEditor", "Text Viewer or Snip Content is null");
        }
    }

    /**
     * Gets the location.
     * 
     * @return the location
     */
    public String getLocation() {
        return location;
    }

    /**
     * Gets the snip.
     * 
     * @return the snip
     */
    public SnipBean getSnip() {
        return snip;
    }

    /**
     * Gets the editor area.
     * 
     * @return the editor area
     */
    public Composite getEditorArea() {
        return editorArea;
    }

    /**
     * Gets the tab item.
     * 
     * @return the tab item
     */
    public CTabItem getTabItem() {
        return tabItem;
    }

    private static final char CTRL_S = 'S' - '@';

    private Listener createToolbarCommandHandler() {

        return new Listener() {
            public void handleEvent(Event event) {
                if (event.type == SWT.KeyUp) {
                    if ((event.stateMask & SWT.CTRL) != 0) { // CTRL key held
                        if ((event.stateMask & SWT.SHIFT) != 0) { // SHIFT key held
                            switch (event.character) {
                            case CTRL_S: // Save As...

                                break;
                            }
                        }
                    }
                }
            }
        };
    }

    /**
     * @return the changeFlag
     */
    public boolean isChangeFlag() {
        return changeFlag;
    }

    /**
     * @return the abortButton
     */
    public Button getAbortButton() {
        return abortButton;
    }

    /**
     * @return the saveButton
     */
    public Button getSaveButton() {
        return saveButton;
    }

}
